package common

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

func RunNpmUpload(config *NexusConfig) {
	// 开始计时
	start := time.Now()
	var logMap map[string]bool = initNpmLog()
	// 查找所有tar.gz和tgz文件
	files, err := FindPackageFiles(config.NpmRepo)
	if err != nil {
		log.Fatalf("查找包文件失败: %v", err)
	}

	if len(files) == 0 {
		log.Println("未找到tar.gz或tgz文件")
		return
	}

	fmt.Printf("找到 %d 个包文件，将使用 %d 个并发进行上传...\n", len(files), config.Concurrency)

	// 创建任务通道
	taskChan := make(chan string, len(files))
	logChan := make(chan logTask, 1000)
	for _, file := range files {
		fileName := filepath.Base(file)
		if !logMap[fileName] {
			taskChan <- file
		}
	}
	close(taskChan)
	// 使用WaitGroup等待所有goroutine完成
	var wg sync.WaitGroup
	var wgLog sync.WaitGroup
	//启动一个协程负载写入日志
	wgLog.Add(1)
	go writeToNpmLog(logChan, &wgLog)
	var (
		successCount uint64
		failCount    uint64
	)
	suceessMutex := sync.Mutex{}
	errorMutex := sync.Mutex{}

	// 启动并发上传goroutines
	for i := 0; i < config.Concurrency; i++ {
		wg.Add(1)
		go func(workerID int) {
			defer wg.Done()
			for file := range taskChan {
				startTime := time.Now()
				//fmt.Printf("Worker %d: 开始上传 %s 到 %s\n", workerID, file, config.NpmURL)
				err := UploadPackage(file, config.NpmURL, config.NpmRcPath, logChan)
				if err != nil {
					errorMutex.Lock()
					failCount++
					errorMutex.Unlock()
					fmt.Printf("Worker %d: ❌ 上传失败:%d  %s: %v\n", workerID, failCount, file, err)
				} else {
					suceessMutex.Lock()
					successCount++
					suceessMutex.Unlock()
					elapsed := time.Since(startTime)
					fmt.Printf("Worker %d: ✅ 上传成功：%d / %d  %s (耗时: %.2fs)\n", workerID, successCount, failCount+successCount, file, elapsed.Seconds())
				}
			}
		}(i)
	}

	// 等待所有goroutine完成
	wg.Wait()
	fmt.Println("\n上传任务全部完成")
	close(logChan)
	wgLog.Wait()
	fmt.Println("日志写入完成")
	// 输出结果统计
	totalElapsed := time.Since(start)
	log.Printf("上传完成统计:\n\t\t总耗时: %s\n\t\t成功: %d 个\n\t\t失败: %d 个",
		totalElapsed, successCount, failCount,
	)
}

func initNpmLog() map[string]bool {
	var err error
	logName := "npm_log.txt"
	recordName := ".npm.txt"
	logFile, err = os.Create(logName)
	if err != nil {
		log.Fatalf("创建日志文件失败: %v", err)
	}

	recordFile, err = os.OpenFile(recordName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		if !os.IsExist(err) {
			log.Fatalf("创建文件失败: %v", err)
		}
	}
	dataMap := make(map[string]bool) // 使用空结构体节省内存
	scanner := bufio.NewScanner(recordFile)
	for scanner.Scan() {
		line := scanner.Text()
		dataMap[line] = true // 存储行内容作为键
	}
	if err := scanner.Err(); err != nil {
		fmt.Printf("读取文件失败: %v\n", err)
		return dataMap
	}
	fmt.Printf("共读取 %d 行记录\n", len(dataMap))
	return dataMap
}

// FindPackageFiles 查找目录下所有npm包文件
func FindPackageFiles(dir string) ([]string, error) {
	var files []string

	// 查找tar.gz文件
	tarGzFiles, err := filepath.Glob(filepath.Join(dir, "*.tar.gz"))
	if err != nil {
		return nil, err
	}
	files = append(files, tarGzFiles...)

	// 查找tgz文件
	tgzFiles, err := filepath.Glob(filepath.Join(dir, "*.tgz"))
	if err != nil {
		return nil, err
	}
	files = append(files, tgzFiles...)
	return files, nil
}

// UploadPackage 上传单个npm包到指定registry
func UploadPackage(filePath, registryURL, npmrcPath string, logChan chan logTask) error {
	if registryURL == "" || npmrcPath == "" {
		return errors.New("上传npm包需要提供仓库地址和.npmrc路径")
	}
	// 创建命令
	cmd := exec.Command("npm", "publish", "--registry", registryURL, filePath, "--userconfig", npmrcPath)

	// 捕获命令输出
	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr
	logTask := logTask{
		message: strings.Join(cmd.Args, " "),
		name:    "",
	}
	// 执行命令
	err := cmd.Run()
	if err != nil {
		logChan <- logTask
		return fmt.Errorf("命令执行失败: %v, 输出: %s", err, stderr.String())
	}
	// 检查输出是否包含错误信息
	output := out.String() + stderr.String()
	if strings.Contains(output, "npm error") {
		logChan <- logTask
		return fmt.Errorf("npm publish返回错误: %s", output)
	}
	logTask.name = filepath.Base(filePath)
	logChan <- logTask
	return nil
}

func writeToNpmLog(logChan chan logTask, wgLog *sync.WaitGroup) {
	defer func() {
		logFile.Close()
		recordFile.Close()
		wgLog.Done()
	}()
	// 使用 for-range 持续读取通道，直到通道关闭
	for logTask := range logChan {
		fileMutex.Lock()
		_, err := logFile.WriteString(logTask.message + "\n")
		if err != nil {
			log.Printf("写入日志失败: %v", err)
		}
		if logTask.name != "" {
			_, err = recordFile.WriteString(logTask.name + "\n")
			if err != nil {
				log.Printf("写入日志失败1: %v", err)
			}
		}
		fileMutex.Unlock()
	}
}
